// I2C testbench
// 版本：V2.0
// 作者：杨纯一
// 日期：2025-9-30

`timescale 1ns/1ps

module i2c_master_v2_tb;

    // DUT 接口信号
    reg clk;
    reg reset;
    reg i_req;
    reg i_rw;             // 0=写 1=读
    reg i_fast_mode;      // 0=100kHz 1=400kHz
    reg [6:0] i_dev_addr;
    reg [7:0] i_data;
    wire [7:0] o_data;
    wire o_busy;
    wire o_ack_error;

    // I2C 总线
    wire sda;
    wire scl;

    // 从设备模型控制
    reg slave_ack_enable;      // 控制是否应答（用于制造NACK场景）
    reg [7:0] slave_mem_byte;  // 简单单字节存储
    reg [7:0] bit_shift_reg;   // 发送/接收移位
    reg [3:0] bit_cnt;         // 位计数（仅在从设备内部使用）
    reg sda_drv_en;            // 从设备是否驱动 SDA (开漏)
    reg sda_drv_dat;           // 从设备驱动数据（0有效）
    reg [2:0] phase_state;     // 简单阶段：0空闲 1接收地址 2接收数据 3发送数据 4等待STOP
    reg rw_latched;            // 记录本次操作方向


    assign sda = (sda_drv_en && (sda_drv_dat==1'b0)) ? 1'b0 : 1'bz; 

    // 实例化 DUT
    i2c_master dut (
        .clk(clk),
        .reset(reset),
        .i_req(i_req),
        .i_rw(i_rw),
        .i_fast_mode(i_fast_mode),
        .i_dev_addr(i_dev_addr),
        .i_data(i_data),
        .o_data(o_data),
        .o_busy(o_busy),
        .o_ack_error(o_ack_error),
        .sda(sda),
        .scl(scl)
    );

    // 生成系统时钟 (50MHz => 20ns周期)
    initial begin
        clk = 0;
        forever #10 clk = ~clk;
    end

    // 复位
    initial begin
        reset = 1;
        #200;
        reset = 0;
    end

    // 从设备简单状态机 (极简，仅支持单字节读/写，应答)
    // 约束：不处理时钟延展，不处理重复起始，多字节只保留最后一个
    reg sda_prev;
    reg scl_prev;
    wire sda_in = sda; // 读取总线
    wire scl_in = scl; // 读取总线

    // 采样前一拍
    always @(posedge clk) begin
        sda_prev <= sda_in;
        scl_prev <= scl_in;
    end

    // 起始 / 停止条件检测
    wire start_cond = (sda_prev==1'b1 && sda_in==1'b0 && scl_in==1'b1);
    wire stop_cond  = (sda_prev==1'b0 && sda_in==1'b1 && scl_in==1'b1);

    // 从设备行为：在SCL上升沿采样，在SCL下降沿准备下一位
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            phase_state <= 0;
            bit_cnt <= 0;
            sda_drv_en <= 0;
            sda_drv_dat <= 1'b1;
            slave_mem_byte <= 8'h5A; // 预置一个读出值
            slave_ack_enable <= 1'b1;
            bit_shift_reg <= 0;
            rw_latched <= 0;
        end else begin
            if (start_cond) begin
                // 起始 -> 准备接收地址 + R/W
                phase_state <= 1;
                bit_cnt <= 0;
                sda_drv_en <= 0; // 释放SDA
            end else if (stop_cond) begin
                phase_state <= 0; // 回到空闲
                sda_drv_en <= 0;
            end else begin
                case (phase_state)
                    0: begin
                        // 空闲：不驱动 SDA
                        sda_drv_en <= 0;
                    end
                    1: begin
                        // 接收地址 + R/W，共8位
                        if (scl_prev==0 && scl_in==1) begin // SCL上升沿采样
                            bit_shift_reg <= {bit_shift_reg[6:0], sda_in};
                            bit_cnt <= bit_cnt + 1;
                            if (bit_cnt == 7) begin
                                // 8位接收完成 -> 发送ACK
                                rw_latched <= sda_in; // 最后一位是 R/W
                                phase_state <= 2; // 下一阶段：根据读写方向决定
                                bit_cnt <= 0;
                                // 准备ACK：在下一个SCL低期间拉低SDA
                            end
                        end
                        // ACK 时序：在完整接收 8 位后的下一个SCL低相位拉低 SDA
                        if (bit_cnt == 0 && rw_latched==rw_latched) begin
                            // 仅占位，防综合警告
                        end
                        // 在 SCL 下降沿准备 ACK (地址阶段结束)
                        if (scl_prev==1 && scl_in==0 && bit_cnt==0 && phase_state==2) begin
                            if (slave_ack_enable) begin
                                sda_drv_en <= 1; sda_drv_dat <= 0; // ACK
                            end else begin
                                sda_drv_en <= 0; // NACK
                            end
                        end
                        // 在SCL再次上升后释放 SDA
                        if (scl_prev==0 && scl_in==1 && phase_state==2) begin
                            if (slave_ack_enable) begin
                                sda_drv_en <= 0; // 释放 SDA
                            end
                        end
                    end
                    2: begin
                        // 根据读写方向执行：rw_latched=0 -> 主写(从接收数据)，=1 -> 主读(从发送数据)
                        if (rw_latched==1'b0) begin
                            // 接收主机写入的 8 位数据 + ACK
                            if (scl_prev==0 && scl_in==1) begin
                                bit_shift_reg <= {bit_shift_reg[6:0], sda_in};
                                bit_cnt <= bit_cnt + 1;
                                if (bit_cnt == 7) begin
                                    slave_mem_byte <= {bit_shift_reg[6:0], sda_in};
                                    bit_cnt <= 0;
                                    phase_state <= 4; // 等待停止（单字节）
                                end
                            end
                            // 发送ACK：数据 8 位完成后的下一个 SCL 低期间拉低
                            if (scl_prev==1 && scl_in==0 && phase_state==4) begin
                                if (slave_ack_enable) begin
                                    sda_drv_en <= 1; sda_drv_dat <= 0; // ACK
                                end else begin
                                    sda_drv_en <= 0; // NACK
                                end
                            end
                            if (scl_prev==0 && scl_in==1 && phase_state==4) begin
                                sda_drv_en <= 0; // 释放
                            end
                        end else begin
                            // 读：从设备在SCL低时准备数据位，在SCL高被主机采样
                            if (bit_cnt==0 && scl_prev==1 && scl_in==0) begin
                                bit_shift_reg <= slave_mem_byte; // 准备要发送的字节
                            end
                            if (scl_prev==1 && scl_in==0) begin
                                // 在SCL下降沿放置下一位
                                case (bit_cnt)
                                    0: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[7]; end
                                    1: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[6]; end
                                    2: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[5]; end
                                    3: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[4]; end
                                    4: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[3]; end
                                    5: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[2]; end
                                    6: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[1]; end
                                    7: begin sda_drv_en<=1; sda_drv_dat<=slave_mem_byte[0]; end
                                endcase
                            end
                            if (scl_prev==0 && scl_in==1) begin
                                bit_cnt <= bit_cnt + 1;
                                if (bit_cnt==7) begin
                                    bit_cnt <= 0;
                                    phase_state <= 4; // 等待主机发NACK+STOP
                                    sda_drv_en <= 0; // 释放 SDA 等待主机 NACK
                                end
                            end
                        end
                    end
                    4: begin
                        // 等停止条件。
                        sda_drv_en <= 0;
                    end
                endcase
            end
        end
    end

    // 监视
    initial begin
        $monitor("T=%0t BUSY=%b ACK_ERR=%b SDA=%b SCL=%b o_data=%h state_slave=%0d", $time, o_busy, o_ack_error, sda, scl, o_data, phase_state);
    end

    // 激励过程
    initial begin
        i_req = 0; i_rw = 0; i_fast_mode = 0; i_dev_addr = 7'h50; i_data = 8'h00;
        @(negedge reset);
        #500;

        // 1) 写一个字节 0x3C 到地址0x50
        i_data = 8'h3C; i_rw = 0; i_dev_addr = 7'h50; i_req = 1; @(posedge clk); i_req = 0;
        wait(!o_busy); // 等待完成
        #2000;

        // 2) 读一个字节（应返回 slave_mem_byte 当前值）
        i_rw = 1; i_req = 1; @(posedge clk); i_req = 0;
        wait(!o_busy);
        #2000;

        // 3) 制造NACK：关闭从机ACK
        slave_ack_enable = 0;
        i_rw = 0; i_data = 8'h55; i_req = 1; @(posedge clk); i_req = 0;
        wait(!o_busy);
        #2000;
        slave_ack_enable = 1;

        // 4) 再次读（ACK恢复）
        i_rw = 1; i_req = 1; @(posedge clk); i_req = 0; wait(!o_busy);
        #2000;

        $display("TEST FINISH");
        #1000;
        $finish;
    end

    // 波形转储（VCD）
    initial begin
        $dumpfile("i2c_master_v2_tb.vcd");
        $dumpvars(0, i2c_master_v2_tb);
    end

endmodule
